/*
 * Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.bpel.b4p.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.axis2.description.*;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.AxisFault;
import org.apache.axis2.deployment.util.Utils;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.om.OMAbstractFactory;
import org.wso2.carbon.utils.ServerConstants;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.bpel.b4p.axis2.receiver.HumanMessageReceiver;
import org.wso2.carbon.bpel.b4p.extension.B4PExtensionOperation;

import javax.xml.namespace.QName;
import javax.wsdl.*;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.http.HTTPBinding;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.wsdl.extensions.soap.SOAPBinding;
import java.util.Iterator;

public class CallbackServiceFactory {
    private static Log log = LogFactory.getLog(CallbackServiceFactory.class);

    /**
     * Create AxisService for the requested Callback service to retrieve messages from HumanTask.
     *
     * @param serviceName    Callback service QName
     * @param servicePort    Callback service port
     * @param axisConfig     AxisConfiguration object
     * @param wsdlDef        WSDL definition of the Callback Service
     * @return AxisService
     * @throws org.apache.axis2.AxisFault   if service cannot be created
     */
    public static AxisService getCallbackService(QName serviceName, String servicePort, AxisConfiguration axisConfig,
                                                 Definition wsdlDef, Operation responseOperation) throws AxisFault {
        String serviceKey = serviceName.getLocalPart();

        try {
            AxisService service = axisConfig.getService(serviceKey);
            SOAPHelper soapHelper = createSoapHelper(serviceName, servicePort, wsdlDef, responseOperation);
            if (service == null) {
                synchronized (AnonymousServiceFactory.class) {
                    /* Fix for bugs due to high concurrency. If there are number of service calls to same service from
                     * different process instances, two process instances will try to add same service to axis config.
                     */
                    service = axisConfig.getService(serviceKey);
                    if (service != null) {
                        setMessageReceiver(service, soapHelper);
                        return service;
                    }
  
                    service = createCallbackService(serviceName, servicePort, axisConfig, wsdlDef);
                }
            }
            setMessageReceiver(service, soapHelper);
            return service;
        } catch (AxisFault axisFault) {
            log.error("Error creating callback service: " + serviceKey, axisFault);
            throw axisFault;
        }
    }

    private static void setMessageReceiver(AxisService service, SOAPHelper soapHelper) {
        Iterator operations = service.getOperations();
        HumanMessageReceiver msgReceiver = new HumanMessageReceiver();
        msgReceiver.setSoapHelper(soapHelper);

        while (operations.hasNext()) {
            AxisOperation operation = (AxisOperation) operations.next();
            // Setting WSDLAwareMessage Receiver even if operation has a message receiver specified.
            // This is to fix the issue when build service configuration using services.xml(Always RPCMessageReceiver
            // is set to operations).
            operation.setMessageReceiver(msgReceiver);
        }
    }

    private static AxisService createCallbackService(QName serviceName, String servicePort,
                                                     AxisConfiguration axisConfig, Definition wsdlDef) throws AxisFault {
        //Creates axis service
        AxisService axisService = null;
        //From where the wsdl def should be read? I think from registry
        String wsdlBaseURI = wsdlDef.getDocumentBaseURI();
        WSDL11ToAxisServiceBuilder serviceBuilder = new WSDL11ToAxisServiceBuilder(wsdlDef,
                serviceName, servicePort);
        serviceBuilder.setBaseUri(wsdlBaseURI);
        /*we don't need custom resolvers since registry takes care of it*/
        //serviceBuilder.setCustomResolver(new Axis2UriResolver());
        //serviceBuilder.setCustomWSDLResolver(new Axis2WSDLLocator(wsdlBaseURI));
        serviceBuilder.setServerSide(true);
        try {
            axisService = serviceBuilder.populateService();
        } catch (AxisFault axisFault) {
            log.error("Error populating the service", axisFault);
            throw axisFault;
        }

        if (axisService == null) {
            return null;
        }

        axisService.setParent(axisConfig);
        axisService.setWsdlFound(true);
        axisService.setCustomWsdl(true);
        axisService.setClassLoader(axisConfig.getServiceClassLoader());

        Utils.setEndpointsToAllUsedBindings(axisService);

        /*TODO do i need to set file name - url to wsdl in Registry*/
        //axisService.setFileName(wsdlUrl);

        try {
            //axisService.addParameter(new Parameter("useOriginalwsdl", "true"));
            axisService.addParameter(new Parameter("modifyUserWSDLPortAddress", "true"));

            /* Setting service type to use in service management*/
            axisService.addParameter(ServerConstants.SERVICE_TYPE, "bpel");

            /* Task ID as a service parameter to use with process try-it*/
            //axisService.addParameter(Constants.PROCESS_ID, processConf.getProcessId());

            /* Fix for losing of security configuration  when updating human-task package*/
            axisService.addParameter(new Parameter(CarbonConstants.PRESERVE_SERVICE_HISTORY_PARAM, "true"));
        } catch (AxisFault axisFault) {
            log.error("Error adding parameters", axisFault);
            throw axisFault;
        }

//        Iterator operations = axisService.getOperations();
//        HumanMessageReceiver msgReceiver = new HumanMessageReceiver();
//
//        while (operations.hasNext()) {
//            AxisOperation operation = (AxisOperation) operations.next();
//            // Setting WSDLAwareMessage Receiver even if operation has a message receiver specified.
//            // This is to fix the issue when build service configuration using services.xml(Always RPCMessageReceiver
//            // is set to operations).
//            operation.setMessageReceiver(msgReceiver);
//        }
        axisConfig.addServiceGroup(createServiceGroupForService(axisService));

        return axisService;
    }

    private static AxisServiceGroup createServiceGroupForService(AxisService svc) throws AxisFault {
        AxisServiceGroup svcGroup = new AxisServiceGroup();
        svcGroup.setServiceGroupName(svc.getName());
        svcGroup.addService(svc);
        svcGroup.addParameter(new Parameter(CarbonConstants.PRESERVE_SERVICE_HISTORY_PARAM, "true"));

        return svcGroup;
    }

    private static  SOAPHelper createSoapHelper(QName serviceName, String servicePort, Definition wsdlDef,
                                                Operation responseOperation)
            throws AxisFault {
        SOAPFactory soapFactory;
        boolean isRPC = false;

        Service serviceDef = wsdlDef.getService(serviceName);
        if (serviceDef == null) {
            throw new AxisFault(Messages.msgServiceDefinitionNotFound(serviceName.getLocalPart()));
        }
        Port port = serviceDef.getPort(servicePort);
        if (port == null) {
            throw new AxisFault(Messages.msgServicePortNotFound(serviceName.getLocalPart(), servicePort));
        }
        Binding binding = port.getBinding();

        if (binding == null)
            throw new AxisFault(Messages.msgBindingNotFound(serviceName.getLocalPart(), servicePort));

        ExtensibilityElement bindingType = HumanServiceUtil.getBindingExtension(binding);

        if (!(bindingType instanceof SOAPBinding || bindingType instanceof SOAP12Binding ||
                bindingType instanceof HTTPBinding)) {
            throw new AxisFault(Messages.msgBindingNotSupported(serviceName.getLocalPart(), servicePort));
        }

        if (bindingType instanceof SOAPBinding) {
            soapFactory = OMAbstractFactory.getSOAP11Factory();
        } else {
            soapFactory = OMAbstractFactory.getSOAP12Factory();
        }

        if (bindingType instanceof SOAPBinding) {
            isRPC = ((SOAPBinding) bindingType).getStyle() != null &&
                    ((SOAPBinding) bindingType).getStyle().equals("rpc");
        } else if (bindingType instanceof SOAP12Binding) {
            isRPC = ((SOAP12Binding) bindingType).getStyle() != null &&
                    ((SOAP12Binding) bindingType).getStyle().equals("rpc");
        }
        return new SOAPHelper(wsdlDef, binding, serviceName.getLocalPart(), servicePort, soapFactory, isRPC, 
                responseOperation);
    }
}
